
<template>
  <div class="autocomplete-wrapper" :style="{width: width}">
    <input
      ref="input"
      type="text"
      :class="{'isBorder': isBorder}"
      :placeholder="placeholder"
      v-model="type"
      :disabled="disabled"
      @input="handleInput"
      @compositionstart="compositionstartInput"
      @compositionend="compositionendInput"
      @blur="handleBlur"
      @keydown="handleKeyDown"
      @focus="handleFocus"
      autocomplete="off"
    />
    <div
      class="autocomplete-list"
      :class="{'autocomplete-fixed': isFixed, 'autocomplete-absolute': !isFixed}"
      :style="{
        top: `${optionTop}px`,
        left: `${optionLeft}px`,
        width: optionWidth}"
      v-show="showList && json.length">
      <ul>
        <li
          v-for="(data, i) in json"
          :class="activeClass(i)">
          <a
            href="#"
            @click.prevent="selectList(data)"
            @mousemove="mousemove(i)">
            <div>
              <P :title="data[label]">{{ data[label] }}</P>
            </div>
          </a>
        </li>
      </ul>
    </div>
  </div>
</template>


<script>
  export default {
    name: 'zz-auto',
    props: {
      data: Object,
      placeholder: String,
      disabled: {
        type: Boolean,
        default: false
      },
      width: {
        type: String,
        default: '180px'
      },
      isBorder: {
        type: Boolean,
        default: false
      },
      // 是否用fixed css
      isFixed: {
        type: Boolean,
        default: false
      },
      // Manual List
      options: Array,

      // Filter After Get the data
      filterByAnchor: {
        type: Boolean,
        default: false
      },

      // Label of list
      label: String,

      // Debounce time
      debounce: Number,

      // ajax URL will be fetched
      url: {
        type: String
      },

      // query param
      query: '',

      constant: '',

      // Custom Params
      customParams: Object,

      // minimum length
      min: {
        type: Number,
        default: 0
      },
      // Process the result before retrieveng the result array.
      process: Function,
      // Callback
      onInput: Function,
      onShow: Function,
      onBlur: Function,
      onHide: Function,
      onFocus: Function,
      onSelect: Function,
      onBeforeAjax: Function,
      onAjaxProgress: Function,
      onAjaxLoaded: Function,
      onShouldGetData: Function,
      dataObj: [Object, String, Number]
    },

    data () {
      return {
        showList: false,
        isCompositionend: false,
        type: '',
        json: [],
        focusList: '',
        debounceTask: undefined,
        optionTop: 25,
        optionLeft: 0,
        optionWidth: '100%'
      }
    },

    watch: {
      options (newVal) {
        if (this.filterByAnchor) {
          const { type, anchor } = this
          const regex = new RegExp(`${type}`, 'i')
          const filtered = newVal.filter((item) => {
            const found = item[anchor].search(regex) !== -1
            return found
          })
          this.json = filtered
        } else {
          this.json = newVal
        }
      }
    },

    methods: {
      /* ==============================
        INPUT EVENTS
      ============================= */

      compositionstartInput () {
        this.isCompositionend = true
      },

      compositionendInput () {
        this.isCompositionend = false
      },

      handleInput (e) {
        if (this.isCompositionend) {
          return
        }
        if (this.isFixed) {
          this.setFixedPosition(e)
        }
        const { value } = e.target
        this.showList = true
        // Callback Event
        if (this.onInput) this.onInput(value)
        // If Debounce
        if (this.debounce) {
          if (this.debounceTask !== undefined) clearTimeout(this.debounceTask)
          this.debounceTask = setTimeout(() => {
            return this.getData(value)
          }, this.debounce)
        } else {
          return this.getData(value)
        }
      },
      setFixedPosition () {
        const rect = this.$refs.input.getBoundingClientRect()
        this.optionTop = rect.top + 25
        this.optionLeft = rect.left
        this.optionWidth = `${rect.width}px`
      },
      handleKeyDown (e) {
        let key = e.keyCode

        // Disable when list isn't showing up
        if (!this.showList) return

        // Key List
        const DOWN = 40
        const UP = 38
        const ENTER = 13
        const ESC = 27

        // Prevent Default for Prevent Cursor Move & Form Submit
        switch (key) {
          case DOWN:
            e.preventDefault()
            this.focusList++
            break
          case UP:
            e.preventDefault()
            this.focusList--
            break
          case ENTER:
            e.preventDefault()
            this.selectList(this.json[this.focusList])
            this.showList = false
            break
          case ESC:
            this.showList = false
            break
        }

        const listLength = this.json.length - 1
        const outOfRangeBottom = this.focusList > listLength
        const outOfRangeTop = this.focusList < 0
        const topItemIndex = 0
        const bottomItemIndex = listLength

        let nextFocusList = this.focusList
        if (outOfRangeBottom) nextFocusList = topItemIndex
        if (outOfRangeTop) nextFocusList = bottomItemIndex
        this.focusList = nextFocusList
      },

      setValue (val) {
        this.type = val
      },

      /* ==============================
        LIST EVENTS
      ============================= */

      handleBlur (e) {
        // Callback Event
        this.onBlur ? this.onBlur(e, this.data) : null
        setTimeout(() => {
          // Callback Event
          this.onHide ? this.onHide() : null
          this.showList = false
        }, 250)
      },

      handleFocus (e) {
        this.focusList = 0
        // Callback Event
        this.onFocus ? this.onFocus(e) : null
      },

      mousemove (i) {
        this.focusList = i
      },

      activeClass (i) {
        const focusClass = i === this.focusList ? 'focus-list' : ''
        return `${focusClass}`
      },

      selectList (data) {
        // Put the selected data to type (model)
        this.type = data[this.label]
        // Hide List
        this.showList = false
        // Callback Event
        if (this.dataObj) {
          Object.assign(data, {value: this.dataObj})
        }
        this.onSelect ? this.onSelect(data, this.data) : null
      },

      doAjax (val) {
        // Callback Event
        this.onBeforeAjax ? this.onBeforeAjax(val) : null
        let formDate = {}
        if (Array.isArray(this.query)) {
          formDate[this.query[0]] = val.trim()
          formDate[this.query[1]] = this.constant
        } else {
          formDate[this.query] = val.trim()
        }
        this.$axios({
          method: 'post',
          url: this.url,
          data: this.$qs.stringify(formDate, {indices: false})
        }).then(res => {
          if (res.data.errorMsg) {
            this.$message.error(res.data.errorMsg)
          } else {
            let json = res.data.list
            if (json) {
              this.onAjaxLoaded ? this.onAjaxLoaded(json) : null
              this.json = this.process ? this.process(json) : json
            }
          }
        })
      },

      getData (value) {
        if (value.length < this.min || !this.url) return
        if (this.onShouldGetData) {
          this.manualGetData(value)
        } else {
          this.doAjax(value)
        }
      },

      // Do Ajax Manually, so user can do whatever he want
      manualGetData (val) {
        const task = this.onShouldGetData(val)
        if (task && task.then) {
          return task.then((options) => {
            this.json = options
          })
        }
      }
    }
  }
</script>

<style lang="scss" scoped>
  .autocomplete-wrapper{
    display: inline-block;
    vertical-align: top;
    position: relative;
    input{
      width: 100%;
      height: 20px;
      border: none;
    }
    input:disabled{
      background-color: #fff;
    }
    .isBorder{
      @include input-l(100%)
    }
    .autocomplete-list{
      background: #fff;
      z-index: 2;
      height: 200px;
      width: 100%;
      overflow-x: hidden !important;
      overflow-y: auto !important;
      @include shadow;
      @include ellipsis;
      li{
        list-style-type:none;
        line-height: 20px;
        padding: 4px 5px;
      }
      p{
        width: 100%;
      }
      .focus-list{
        background: $color;
        >a{
          >div{
            color: #fff;
          }
        }
      }
    }
    .autocomplete-fixed{
      position: fixed;
    }
    .autocomplete-absolute{
      position: absolute;
    }
  }
</style>
